/** @license
 *
 * jsPDF - PDF Document creation from JavaScript
 * Version 2.5.0 Built on 2021-12-21T09:44:51.866Z
 *                      CommitID 00000000
 *
 * Copyright (c) 2010-2021 James Hall <james@parall.ax>, https://github.com/MrRio/jsPDF
 *               2015-2021 yWorks GmbH, http://www.yworks.com
 *               2015-2021 Lukas Holländer <lukas.hollaender@yworks.com>, https://github.com/HackbrettXXX
 *               2016-2018 Aras Abbasi <aras.abbasi@gmail.com>
 *               2010 Aaron Spike, https://github.com/acspike
 *               2012 Willow Systems Corporation, https://github.com/willowsystems
 *               2012 Pablo Hess, https://github.com/pablohess
 *               2012 Florian Jenett, https://github.com/fjenett
 *               2013 Warren Weckesser, https://github.com/warrenweckesser
 *               2013 Youssef Beddad, https://github.com/lifof
 *               2013 Lee Driscoll, https://github.com/lsdriscoll
 *               2013 Stefan Slonevskiy, https://github.com/stefslon
 *               2013 Jeremy Morel, https://github.com/jmorel
 *               2013 Christoph Hartmann, https://github.com/chris-rock
 *               2014 Juan Pablo Gaviria, https://github.com/juanpgaviria
 *               2014 James Makes, https://github.com/dollaruw
 *               2014 Diego Casorran, https://github.com/diegocr
 *               2014 Steven Spungin, https://github.com/Flamenco
 *               2014 Kenneth Glassey, https://github.com/Gavvers
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Contributor(s):
 *    siefkenj, ahwolf, rickygu, Midnith, saintclair, eaparango,
 *    kim3er, mfo, alnorth, Flamenco
 */

var ApiMode = {
    COMPAT: "compat",
    ADVANCED: "advanced"
};

class Error{
    constructor(message){
        this.errorMessage = message;
    }
}

class jsPDF {
    constructor(options) {
        options = options;
        this.orientation = typeof arguments[0] === "string" ? arguments[0] : "p";
        this.format = typeof arguments[1] === "string" ? arguments[1] : "a4";
        if (typeof options === "object") {
            this.orientation = options.orientation;
            this.format = options.format || this.format;
        }
        this.UNKNOWN = "UNKNOWN";

        // Heuristic selection of a good batch for large array .apply. Not limiting make the call overflow.
        // With too small batch iteration will be slow as more calls are made,
        // higher values cause larger and slower garbage collection.
        this.ARRAY_APPLY_BATCH = 8192;

        this.imageFileTypeHeaders = {
            PNG: [[0x89, 0x50, 0x4e, 0x47]],
            TIFF: [
                [0x4d, 0x4d, 0x00, 0x2a], //Motorola
                [0x49, 0x49, 0x2a, 0x00] //Intel
            ],
            JPEG: [
                [
                    0xff,
                    0xd8,
                    0xff,
                    0xe0,
                    undefined,
                    undefined,
                    0x4a,
                    0x46,
                    0x49,
                    0x46,
                    0x00
                ], //JFIF
                [
                    0xff,
                    0xd8,
                    0xff,
                    0xe1,
                    undefined,
                    undefined,
                    0x45,
                    0x78,
                    0x69,
                    0x66,
                    0x00,
                    0x00
                ], //Exif
                [0xff, 0xd8, 0xff, 0xdb], //JPEG RAW
                [0xff, 0xd8, 0xff, 0xee] //EXIF RAW
            ],
            JPEG2000: [[0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20]],
            GIF87a: [[0x47, 0x49, 0x46, 0x38, 0x37, 0x61]],
            GIF89a: [[0x47, 0x49, 0x46, 0x38, 0x39, 0x61]],
            WEBP: [
                [
                    0x52,
                    0x49,
                    0x46,
                    0x46,
                    undefined,
                    undefined,
                    undefined,
                    undefined,
                    0x57,
                    0x45,
                    0x42,
                    0x50
                ]
            ],
            BMP: [
                [0x42, 0x4d], //BM - Windows 3.1x, 95, NT, ... etc.
                [0x42, 0x41], //BA - OS/2 struct bitmap array
                [0x43, 0x49], //CI - OS/2 struct color icon
                [0x43, 0x50], //CP - OS/2 const color pointer
                [0x49, 0x43], //IC - OS/2 struct icon
                [0x50, 0x54] //PT - OS/2 pointer
            ]
        };
        this.pageFormats = {
            a0: [2383.94, 3370.39],
            a1: [1683.78, 2383.94],
            a2: [1190.55, 1683.78],
            a3: [841.89, 1190.55],
            a4: [595.28, 841.89],
            a5: [419.53, 595.28],
            a6: [297.64, 419.53],
            a7: [209.76, 297.64],
            a8: [147.4, 209.76],
            a9: [104.88, 147.4],
            a10: [73.7, 104.88],
            b0: [2834.65, 4008.19],
            b1: [2004.09, 2834.65],
            b2: [1417.32, 2004.09],
            b3: [1000.63, 1417.32],
            b4: [708.66, 1000.63],
            b5: [498.9, 708.66],
            b6: [354.33, 498.9],
            b7: [249.45, 354.33],
            b8: [175.75, 249.45],
            b9: [124.72, 175.75],
            b10: [87.87, 124.72],
            c0: [2599.37, 3676.54],
            c1: [1836.85, 2599.37],
            c2: [1298.27, 1836.85],
            c3: [918.43, 1298.27],
            c4: [649.13, 918.43],
            c5: [459.21, 649.13],
            c6: [323.15, 459.21],
            c7: [229.61, 323.15],
            c8: [161.57, 229.61],
            c9: [113.39, 161.57],
            c10: [79.37, 113.39],
            dl: [311.81, 623.62],
            letter: [612, 792],
            "government-letter": [576, 756],
            legal: [612, 1008],
            "junior-legal": [576, 360],
            ledger: [1224, 792],
            tabloid: [792, 1224],
            "credit-card": [153, 243]
        };
        this. jsPDFAPI = [];
        this.image_compression = {
            NONE: "NONE",
            FAST: "FAST",
            MEDIUM: "MEDIUM",
            SLOW: "SLOW"
        };
        this.jsPDFAPI["decode"] = {
            DCT_DECODE: "DCTDecode",
            FLATE_DECODE: "FlateDecode",
            LZW_DECODE: "LZWDecode",
            JPX_DECODE: "JPXDecode",
            JBIG2_DECODE: "JBIG2Decode",
            ASCII85_DECODE: "ASCII85Decode",
            ASCII_HEX_DECODE: "ASCIIHexDecode",
            RUN_LENGTH_DECODE: "RunLengthDecode",
            CCITT_FAX_DECODE: "CCITTFaxDecode"
        };
        this.color_spaces = {
            DEVICE_RGB: "DeviceRGB",
            DEVICE_GRAY: "DeviceGray",
            DEVICE_CMYK: "DeviceCMYK",
            CAL_GREY: "CalGray",
            CAL_RGB: "CalRGB",
            LAB: "Lab",
            ICC_BASED: "ICCBased",
            INDEXED: "Indexed",
            PATTERN: "Pattern",
            SEPARATION: "Separation",
            DEVICE_N: "DeviceN"
        };

        this.markers = [0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7];

        this.filters = [];
        this.pdfVersion = "1.3";
        this.version = "0.0.0";
        this.fileId = "00000000000000000000000000000000";
        this.documentProperties = {
            title: "",
            subject: "",
            author: "",
            keywords: "",
            creator: ""
        };
        this.creationDate = this.convertDateToPDFDate(new Date());
        this.encryptionOptions = null;
        this.objectNumber = 0;
        this.renderTargets = {};
        this.renderTargetMap = {};
        this.renderTargetStack = [];
        this.offsets = [];
        this.content = [];
        this.contentLength = 0;
        this.additionalObjects = [];
        this.floatPrecision = 16;
        this.outputDestination = [];
        this.apiMode = ApiMode.COMPAT;
        this.scaleFactor = 1;
        this.page = 0;
        this.pages = [];
        this.pagesContext = [];
        this.currentPage = 1;
        this.zoomMode = 1;
        this.layoutMode = 'continuous';
        this.pageMode ='UseOutlines';
        this.userUnit = 1.0;
        this.hasCustomDestination = false;
        this.outputDestination = this.content;
        this.lineWidth = 0.200025;
        this.strokeColor = "0 G";
        this.lineCapID = 0;
        this.lineJoinID = 0;
        this.precision;
        this.rootDictionaryObjId = this.newObjectDeferred();
        this.resourceDictionaryObjId = this.newObjectDeferred();
        this.collections=[];
        this.namespace = "addImage_";
        this._addPage(this.format, this.orientation);
    }

    isArrayBuffer(object) {
        return this.supportsArrayBuffer() && object instanceof ArrayBuffer;
    }

    processJPEG (data, index, alias, compression, dataAsBinaryString, colorSpace) {
        var filter = this.jsPDFAPI.decode.DCT_DECODE,
            bpc = 8,
            dims,
            result = null;

        if (typeof data === "string" || this.isArrayBuffer(data) || this.isArrayBufferView(data)) {
            // if we already have a stored binary string rep use that
            data = dataAsBinaryString || data;
            data = this.isArrayBuffer(data) ? new Uint8Array(data) : data;
            data = this.isArrayBufferView(data) ? this.arrayBufferToBinaryString(data) : data;
            dims = this.getJpegInfo(data);

            switch (dims.numcomponents) {
                case 1:
                    colorSpace = this.color_spaces.DEVICE_GRAY;
                    break;

                case 4:
                    colorSpace = this.color_spaces.DEVICE_CMYK;
                    break;

                case 3:
                    colorSpace = this.color_spaces.DEVICE_RGB;
                    break;
            }

            result = {
                data: data,
                width: dims.width,
                height: dims.height,
                colorSpace: colorSpace,
                bitsPerComponent: bpc,
                filter: filter,
                index: index,
                alias: alias
            };
        }

        return result;
    }

    unescape (str) {
        return (str + '==='.slice((str.length + 3) % 4))
            .replace(/-/g, '+')
            .replace(/_/g, '/')
    }

    getJpegInfo(imgData) {
        var width, height, numcomponents;
        var blockLength = imgData.charCodeAt(4) * 256 + imgData.charCodeAt(5);
        var len = imgData.length;
        var result = {
            width: 0,
            height: 0,
            numcomponents: 1
        };

        for (var i = 4; i < len; i += 2) {
            i += blockLength;

            if (this.markers.indexOf(imgData.charCodeAt(i + 1)) !== -1) {
                height = imgData.charCodeAt(i + 5) * 256 + imgData.charCodeAt(i + 6);
                width = imgData.charCodeAt(i + 7) * 256 + imgData.charCodeAt(i + 8);
                numcomponents = imgData.charCodeAt(i + 9);
                result = {
                    width: width,
                    height: height,
                    numcomponents: numcomponents
                };
                break;
            } else {
                blockLength = imgData.charCodeAt(i + 2) * 256 + imgData.charCodeAt(i + 3);
            }
        }

        return result;
    }

    determineWidthAndHeight(width, height, image) {
        if (!width && !height) {
            width = -96;
            height = -96;
        }
        if (width < 0) {
            width = (-1 * image.width * 72) / width / this.internal.scaleFactor;
        }
        if (height < 0) {
            height = (-1 * image.height * 72) / height / this.internal.scaleFactor;
        }
        if (width === 0) {
            width = (height * image.width) / image.height;
        }
        if (height === 0) {
            height = (width * image.height) / image.width;
        }

        return [width, height];
    };

    getImages() {
        var images = this.collections[this.namespace + "images"];
        this.initialize();
        return images;
    };

    writeImageToPDF(x, y, width, height, image, rotation) {
        var dims = this.determineWidthAndHeight(width, height, image);
        //coord = jsPdf.getCoordinateString,
        //vcoord = jsPdf.getVerticalCoordinateString;

        var images = this.getImages();

        width = dims[0];
        height = dims[1];
        images[image.index] = image;

        if (rotation) {
            rotation *= Math.PI / 180;
            var c = Math.cos(rotation);
            var s = Math.sin(rotation);
            //like in pdf Reference do it 4 digits instead of 2
            var f4 = function(number) {
                return number.toFixed(4);
            };
            var rotationTransformationMatrix = [
                f4(c),
                f4(s),
                f4(s * -1),
                f4(c),
                0,
                0,
                "cm"
            ];
        }
        this.write("q"); //Save graphics state
        if (rotation) {
            this.internal.write(
            [1, "0", "0", 1, this.getCoordinateString(x), this.getVerticalCoordinateString(y + height), "cm"].join(" ")
            ); //Translate
            this.internal.write(rotationTransformationMatrix.join(" ")); //Rotate
            this.internal.write(
            [this.getCoordinateString(width), "0", "0", this.getCoordinateString(height), "0", "0", "cm"].join(" ")
            ); //Scale
        } else {
            this.write(
            [
                this.getCoordinateString(width),
                "0",
                "0",
                this.getCoordinateString(height),
                this.getCoordinateString(x),
                this.getVerticalCoordinateString(y + height),
                "cm"
            ].join(" ")
            ); //Translate and Scale
        }

        if (this.isAdvancedAPI()) {
            // draw image bottom up when in "advanced" API mode
            this.internal.write([1, 0, 0, -1, 0, 0, "cm"].join(" "));
        }

        this.write("/I" + image.index + " Do"); //Paint Image
        this.write("Q"); //Restore graphics state
    };

    initialize() {
        if (!this.collections[this.namespace + "images"]) {
            this.collections[this.namespace + "images"] = {};
            //this.internal.events.subscribe("putResources", putResourcesCallback);
            //this.internal.events.subscribe("putXobjectDict", putXObjectsDictCallback);
        }
    };

    notDefined(value) {
        return typeof value === "undefined" || value === null || value.length === 0;
    };

    generateAliasFromImageData(imageData) {
        if (typeof imageData === "string" || this.isArrayBufferView(imageData)) {
            return this.sHashCode(imageData);
        } else if (this.isArrayBufferView(imageData.data)) {
            return this.sHashCode(imageData.data);
        }

        return null;
    }

    sHashCode(data) {
        var hash = 0,
            i,
            len;

        if (typeof data === "string") {
            len = data.length;
            for (i = 0; i < len; i++) {
                hash = (hash << 5) - hash + data.charCodeAt(i);
                hash |= 0; // Convert to 32bit integer
            }
        } else if (this.isArrayBufferView(data)) {
            len = data.byteLength / 2;
            for (i = 0; i < len; i++) {
                hash = (hash << 5) - hash + data[i];
                hash |= 0; // Convert to 32bit integer
            }
        }
        return hash;
    }

    getImageIndex() {
        return Object.keys(this.collections[this.namespace + "images"]).length;
    };

    binaryStringToUint8Array(binary_string) {
        var len = binary_string.length;
        var bytes = new Uint8Array(len);
        for (var i = 0; i < len; i++) {
            bytes[i] = binary_string.charCodeAt(i);
        }
        return bytes;
    }

    checkImagesForAlias(alias) {
        var images = this.collections[this.namespace + "images"];
        if (images) {
            for (var e in images) {
                if (alias === images[e].alias) {
                    return images[e];
                }
            }
        }
    }

    isImageTypeSupported(type) {
        return type.toUpperCase() == "JPEG" || type.toUpperCase() == "PNG";
    };

    extractImageFromDataUrl(dataUrl) {
        dataUrl = dataUrl || "";
        var dataUrlParts = dataUrl.split("base64,");
        var result = null;

        if (dataUrlParts.length === 2) {
            var extractedInfo = /^data:(\w*\/\w*);*(charset=(?!charset=)[\w=-]*)*;*$/.exec(
                dataUrlParts[0]
            );
            if (Array.isArray(extractedInfo)) {
                result = {
                    mimeType: extractedInfo[1],
                    charset: extractedInfo[2],
                    data: dataUrlParts[1]
                };
            }
        }
        return result;
    }

    _utf8_decode(utftext) {
        var string = "", i = 0, c = 0, c1 = 0, c2 = 0;

        while ( i < utftext.length ) {

            c = utftext.charCodeAt(i);

            if (c < 128) {

                string += String.fromCharCode(c);
                i++;

            } else if((c > 191) && (c < 224)) {

                c1 = utftext.charCodeAt(i+1);
                string += String.fromCharCode(((c & 31) << 6) | (c1 & 63));
                i += 2;

            } else {

                c1 = utftext.charCodeAt(i+1);
                c2 = utftext.charCodeAt(i+2);
                string += String.fromCharCode(((c & 15) << 12) | ((c1 & 63) << 6) | (c2 & 63));
                i += 3;
            }
        }
        return string;
    };

    lbtoa(input) {
        var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
        var str = String (input);
        for (
        // initialize result and counter
            var block, charCode, idx = 0, map = chars, output = '';
        // if the next str index does not exist:
        //   change the mapping table to "="
        //   check if d has no fractional digits
            str.charAt (idx | 0) || (map = '=', idx % 1);
        // "8 - idx % 1 * 8" generates the sequence 2, 4, 6, 8
            output += map.charAt (63 & block >> 8 - idx % 1 * 8)
        ) {
            charCode = str.charCodeAt (idx += 3 / 4);
            if (charCode > 0xFF) {
                return "";
            }
            block = block << 8 | charCode;
        }
        return output;
    }

    latob(input) {
        var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
        var str = (String (input)).replace (/[=]+$/, ''); // #31: ExtendScript bad parse of /=
        if (str.length % 4 === 1) {
            return "";
        }
        for (
        // initialize result and counters
            var bc = 0, bs, buffer, idx = 0, output = '';
        // get next character
            buffer = str.charAt (idx++); // eslint-disable-line no-cond-assign
        // character found in table? initialize bit storage and add its ascii value;
            ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
            // and if not first of each 4 characters,
            // convert the first 8 bits to one ascii character
            bc++ % 4) ? output += String.fromCharCode (255 & bs >> (-2 * bc & 6)) : 0
        ) {
            // try to find character in table (0-63, not found => -1)
            buffer = chars.indexOf (buffer);
        }
        return output;
    }

    convertBase64ToBinaryString(stringData,throwError) {
        throwError = typeof throwError === "boolean" ? throwError : true;
        var base64Info;
        var imageData = "";
        var rawData;

        if (typeof stringData === "string") {
            base64Info = this.extractImageFromDataUrl(stringData);
            rawData = base64Info !== null ? base64Info.data : stringData;

            try {
                imageData = this.latob(rawData);
            } catch (e) {
                if (throwError) {
                    if (!validateStringAsBase64(rawData)) {
                        throw new Error(
                            "Supplied Data is not a valid base64-String jsPDF.convertBase64ToBinaryString "
                        );
                    } else {
                        throw new Error(
                            "atob-Error in jsPDF.convertBase64ToBinaryString " + e.message
                        );
                    }
                }
            }
        }
        return imageData;
    }

    supportsArrayBuffer() {
        return (
            typeof ArrayBuffer !== "undefined" && typeof Uint8Array !== "undefined"
        );
    }

    getImageFileTypeByImageData(imageData,fallbackFormat) {
        fallbackFormat = fallbackFormat || this.UNKNOWN;
        var i;
        var j;
        var result = this.UNKNOWN;
        var headerSchemata;
        var compareResult;
        var fileType;

        if (
        fallbackFormat === "RGBA" ||
        (imageData.data !== undefined &&
        imageData.data instanceof Uint8ClampedArray &&
        "height" in imageData &&
        "width" in imageData)
        ) {
            return "RGBA";
        }

        if (this.isArrayBufferView(imageData)) {
            for (fileType in this.imageFileTypeHeaders) {
                headerSchemata = this.imageFileTypeHeaders[fileType];
                for (i = 0; i < headerSchemata.length; i += 1) {
                    compareResult = true;
                    for (j = 0; j < headerSchemata[i].length; j += 1) {
                        if (headerSchemata[i][j] === undefined) {
                            continue;
                        }
                        if (headerSchemata[i][j] !== imageData[j]) {
                            compareResult = false;
                            break;
                        }
                    }
                    if (compareResult === true) {
                        result = fileType;
                        break;
                    }
                }
            }
        } else {
            for (fileType in this.imageFileTypeHeaders) {
                headerSchemata = this.imageFileTypeHeaders[fileType];
                for (i = 0; i < headerSchemata.length; i += 1) {
                    compareResult = true;
                    for (j = 0; j < headerSchemata[i].length; j += 1) {
                        if (headerSchemata[i][j] === undefined) {
                            continue;
                        }
                        if (headerSchemata[i][j] !== imageData.charCodeAt(j)) {
                            compareResult = false;
                            break;
                        }
                    }
                    if (compareResult === true) {
                        result = fileType;
                        break;
                    }
                }
            }
        }

        if (result === this.UNKNOWN && fallbackFormat !== this.UNKNOWN) {
            result = fallbackFormat;
        }
        return result;
    }

    processImageData(imageData, format, alias, compression) {
        var result, dataAsBinaryString;

        if (typeof imageData === "string" && this.getImageFileTypeByImageData(imageData) === this.UNKNOWN) {
            imageData = this.unescape(imageData);
            var tmpImageData = this.convertBase64ToBinaryString(imageData, false);

            if (tmpImageData !== "") {
                imageData = tmpImageData;
            } else {
                /*tmpImageData = jsPDFAPI.loadFile(imageData, true);
                if (tmpImageData !== undefined) {
                    imageData = tmpImageData;
                }*/
            }
        }

        //TODO sreeni
        /*if (isDOMElement(imageData)) {
          imageData = getImageDataFromElement(imageData, format);
        }*/

        format = this.getImageFileTypeByImageData(imageData, format);
        if (!this.isImageTypeSupported(format)) {
            throw new Error(
                "addImage does not support files of type '" +
                format +
                "', please ensure that a plugin for '" +
                format +
                "' support is added."
            );
        }

        // now do the heavy lifting

        if (this.notDefined(alias)) {
            alias = this.generateAliasFromImageData(imageData);
        }
        result = this.checkImagesForAlias(alias);

        if (!result) {
            if (this.supportsArrayBuffer()) {
                // no need to convert if imageData is already uint8array
                if (!(imageData instanceof Uint8Array) && format !== "RGBA") {
                    dataAsBinaryString = imageData;
                    imageData = this.binaryStringToUint8Array(imageData);
                }
            }

            if(format.toUpperCase() === "JPEG"){
                result = this.processJPEG(
                    imageData,
                this.getImageIndex(),
                    alias,
                this.checkCompressValue(compression),
                    dataAsBinaryString
                );
            }else if(format.toUpperCase()==="PNG"){
                result = this.processPNG(
                    imageData,
                this.getImageIndex(),
                    alias,
                this.checkCompressValue(compression),
                    dataAsBinaryString
                );
            }
        }

        if (!result) {
            throw new Error("An unknown error occurred whilst processing the image.");
        }
        return result;
    }

    addImage() {
        var imageData, format, x, y, w, h, alias, compression, rotation;

        imageData = arguments[0];
        if (typeof arguments[1] === "number") {
            format = this.UNKNOWN;
            x = arguments[1];
            y = arguments[2];
            w = arguments[3];
            h = arguments[4];
            alias = arguments[5];
            compression = arguments[6];
            rotation = arguments[7];
        } else {
            format = arguments[1];
            x = arguments[2];
            y = arguments[3];
            w = arguments[4];
            h = arguments[5];
            alias = arguments[6];
            compression = arguments[7];
            rotation = arguments[8];
        }

        if (
        typeof imageData === "object" &&
        "imageData" in imageData
        ) {
            var options = imageData;

            imageData = options.imageData;
            format = options.format || format || UNKNOWN;
            x = options.x || x || 0;
            y = options.y || y || 0;
            w = options.w || options.width || w;
            h = options.h || options.height || h;
            alias = options.alias || alias;
            compression = options.compression || compression;
            rotation = options.rotation || options.angle || rotation;
        }

        //If compression is not explicitly set, determine if we should use compression
        //TODO sreeni
        var filter = [];
        if (compression === undefined && filter.indexOf("FlateEncode") !== -1) {
            compression = "SLOW";
        }

        if (isNaN(x) || isNaN(y)) {
            throw new Error("Invalid coordinates passed to jsPDF.addImage");
        }

        this.initialize();

        var image = this.processImageData(
            imageData,
            format,
            alias,
            compression
        );

        this.writeImageToPDF(x, y, w, h, image, rotation);
    }

    isArrayBufferView(object) {
        return (
            this.supportsArrayBuffer() &&
            typeof Uint32Array !== "undefined" &&
            (object instanceof Int8Array ||
            object instanceof Uint8Array ||
            (typeof Uint8ClampedArray !== "undefined" &&
            object instanceof Uint8ClampedArray) ||
            object instanceof Int16Array ||
            object instanceof Uint16Array ||
            object instanceof Int32Array ||
            object instanceof Uint32Array ||
            object instanceof Float32Array ||
            object instanceof Float64Array)
        );
    }

    checkCompressValue(value) {
        if (value && typeof value === "string") value = value.toUpperCase();
        //TODO sreeni
        //return value in jsPDFAPI.image_compression ? value : image_compression.NONE;
        return this.image_compression.NONE;
    }

    arrayBufferToBinaryString(buffer) {
        var out = "";
        // There are calls with both ArrayBuffer and already converted Uint8Array or other BufferView.
        // Do not copy the array if input is already an array.
        var buf = this.isArrayBufferView(buffer) ? buffer : new Uint8Array(buffer);
        for (var i = 0; i < buf.length; i += this.ARRAY_APPLY_BATCH) {
            // Limit the amount of characters being parsed to prevent overflow.
            // Note that while TextDecoder would be faster, it does not have the same
            // functionality as fromCharCode with any provided encodings as of 3/2021.
            out += String.fromCharCode.apply(
                null,
            buf.subarray(i, i + this.ARRAY_APPLY_BATCH)
            );
        }
        return out;
    }

    f2(number) {
        if (isNaN(number)) {
            throw new Error("Invalid argument passed to jsPDF.f2");
        }
        return this.roundToPrecision(number, 2);
    }

    padd2(number) {
        return ("0" + parseInt(number)).slice(-2);
    }

    convertDateToPDFDate = function(parmDate) {
        var result = "";
        var tzoffset = parmDate.getTimezoneOffset(),
            tzsign = tzoffset < 0 ? "+" : "-",
            tzhour = Math.floor(Math.abs(tzoffset / 60)),
            tzmin = Math.abs(tzoffset % 60),
            timeZoneString = [tzsign, this.padd2(tzhour), "'", this.padd2(tzmin), "'"].join("");

        result = [
            "D:",
            parmDate.getFullYear(),
            this.padd2(parmDate.getMonth() + 1),
            this.padd2(parmDate.getDate()),
            this.padd2(parmDate.getHours()),
            this.padd2(parmDate.getMinutes()),
            this.padd2(parmDate.getSeconds()),
            timeZoneString
        ].join("");
        return result;
    }

    newObject() {
        var oid = this.newObjectDeferred();
        this.newObjectDeferredBegin(oid, true);
        return oid;
    };

    newObjectDeferred() {
        this.objectNumber++;
        this.offsets[this.objectNumber] = function() {
            return this.contentLength;
        };
        return this.objectNumber;
    }

    newObjectDeferredBegin(oid, doOutput) {
        doOutput = typeof doOutput === "boolean" ? doOutput : false;
        this.offsets[oid] = this.contentLength;
        if (doOutput) {
            this.out(oid + " 0 obj");
        }
        return oid;
    }

    _addPage(parmFormat, parmOrientation) {
        var dimensions, width, height;

        this.orientation = parmOrientation || this.orientation;

        if (typeof parmFormat === "string") {
            dimensions = this.pageFormats[parmFormat.toLowerCase()];
            if (Array.isArray(dimensions)) {
                width = dimensions[0];
                height = dimensions[1];
            }
        }

        if (Array.isArray(parmFormat)) {
            width = parmFormat[0] * this.scaleFactor;
            height = parmFormat[1] * this.scaleFactor;
        }

        if (isNaN(width)) {
            width = format[0];
            height = format[1];
        }

        if (width > 14400 || height > 14400) {
            console.warn(
                "A page in a PDF can not be wider or taller than 14400 userUnit. jsPDF limits the width/height to 14400"
            );
            width = Math.min(14400, width);
            height = Math.min(14400, height);
        }

        this.format = [width, height];

        switch (this.orientation.substr(0, 1)) {
            case "l":
                if (height > width) {
                    this.format = [height, width];
                }
                break;
            case "p":
                if (width > height) {
                    this.format = [height, width];
                }
                break;
        }

        this.beginPage(this.format);

        // Set line width
        this.setLineWidth(this.lineWidth);
        // Set draw color
        this.out(this.strokeColor);
        // resurrecting non-default line caps, joins
        if (this.lineCapID !== 0) {
            this.out(this.lineCapID + " J");
        }
        if (this.lineJoinID !== 0) {
            this.out(lineJoinID + " j");
        }
    };

    beginPage(format) {
        this.pages[++this.page] = [];
        this.pagesContext[this.page] = {
            objId: 0,
            contentsObjId: 0,
            userUnit: Number(this.userUnit),
            artBox: null,
            bleedBox: null,
            cropBox: null,
            trimBox: null,
            mediaBox: {
                bottomLeftX: 0,
                bottomLeftY: 0,
                topRightX: Number(this.format[0]),
                topRightY: Number(this.format[1])
            }
        };
        this._setPage(this.page);
        this.setOutputDestination(this.pages[this.currentPage]);
    }

    setOutputDestination(destination) {
        if (!this.hasCustomDestination) {
            this.outputDestination = destination;
        }
    }

    _setPage(n) {
        if (n > 0 && n <= this.page) {
            this.currentPage = n;
        }
    }

    setLineWidth(width) {
        this.lineWidth = width;
        this.out(this.hpf(this.scale(width)) + " w");
        return this;
    }

    scale(number) {
        if (isNaN(number)) {
            throw new Error("Invalid argument passed to jsPDF.scale");
        }
        if (this.apiMode === ApiMode.COMPAT) {
            return number * this.scaleFactor;
        } else if (apiMode === ApiMode.ADVANCED) {
            return number;
        }
    }

    roundToPrecision(number,parmPrecision) {
        var tmpPrecision = this.precision || parmPrecision;
        if (isNaN(number) || isNaN(tmpPrecision)) {
            throw new Error("Invalid argument passed to jsPDF.roundToPrecision");
        }
        return number.toFixed(tmpPrecision).replace(/0+$/, "");
    }
    // high precision float
    hpf(number) {
        if (isNaN(number)) {
            throw new Error("Invalid argument passed to jsPDF.hpf");
        }
        return this.roundToPrecision(number, this.floatPrecision);
    }

    getCoordinateString(value){
        return this.getHorizontalCoordinate(value);
    }

    getHorizontalCoordinate(value) {
        return this.scale(value);
    }

    getHorizontalCoordinateString(value) {
        return this.hpf(tis.getHorizontalCoordinate(value));
    }

    getVerticalCoordinate(value) {
        if (this.apiMode === ApiMode.ADVANCED) {
            return value;
        } else {
            var pageHeight =
                this.pagesContext[this.currentPage].mediaBox.topRightY -
                this.pagesContext[this.currentPage].mediaBox.bottomLeftY;
            return pageHeight - this.scale(value);
        }
    }

    getVerticalCoordinateString(value) {
        return this.hpf(this.getVerticalCoordinate(value));
    }

    out(string) {
        string = string.toString();
        this.contentLength += string.length + 1;
        this.outputDestination.push(string);

        return this.outputDestination;
    }

    write(value) {
        return this.out(
                arguments.length === 1
                ? value.toString()
                : Array.prototype.join.call(arguments, " ")
        );
    }

    isAdvancedAPI() {
        return this.apiMode === ApiMode.ADVANCED;
    };

    putHeader() {
        this.out("%PDF-" + this.pdfVersion);
        this.out("%\xBA\xDF\xAC\xE0");
    }

    putXObject(xObject) {
        xObject.objectNumber = newObject();

        var options = [];
        options.push({ key: "Type", value: "/XObject" });
        options.push({ key: "Subtype", value: "/Form" });
        options.push({
            key: "BBox",
            value:
            "[" +
            [
                this.hpf(xObject.x),
                this.hpf(xObject.y),
                this.hpf(xObject.x + xObject.width),
                this.hpf(xObject.y + xObject.height)
            ].join(" ") +
            "]"
        });
        options.push({
            key: "Matrix",
            value: "[" + xObject.matrix.toString() + "]"
        });
        // TODO: /Resources

        var stream = xObject.pages[1].join("\n");
        this.putStream({
            data: stream,
            additionalKeyValues: options,
            objectId: xObject.objectNumber
        });
        this.out("endobj");
    };

    putXObjects() {
        for (var xObjectKey in this.renderTargets) {
            if (this.renderTargets.hasOwnProperty(xObjectKey)) {
                this.putXObject(this.renderTargets[xObjectKey]);
            }
        }
    }

    toPDFName(str) {
        // eslint-disable-next-line no-control-regex
        if (/[^\u0000-\u00ff]/.test(str)) {
            // non ascii string
            throw new Error(
                "Invalid PDF Name Object: " + str + ", Only accept ASCII characters."
            );
        }
        var result = "",
            strLength = str.length;
        for (var i = 0; i < strLength; i++) {
            var charCode = str.charCodeAt(i);
            if (
            charCode < 0x21 ||
            charCode === 0x23 /* # */ ||
            charCode === 0x25 /* % */ ||
            charCode === 0x28 /* ( */ ||
            charCode === 0x29 /* ) */ ||
            charCode === 0x2f /* / */ ||
            charCode === 0x3c /* < */ ||
            charCode === 0x3e /* > */ ||
            charCode === 0x5b /* [ */ ||
            charCode === 0x5d /* ] */ ||
            charCode === 0x7b /* { */ ||
            charCode === 0x7d /* } */ ||
            charCode > 0x7e
            ) {
                // Char    CharCode    hexStr   paddingHexStr    Result
                // "\t"    9           9        09               #09
                // " "     32          20       20               #20
                // "©"     169         a9       a9               #a9
                var hexStr = charCode.toString(16),
                    paddingHexStr = ("0" + hexStr).slice(-2);

                result += "#" + paddingHexStr;
            } else {
                // Other ASCII printable characters between 0x21 <= X <= 0x7e
                result += str[i];
            }
        }
        return result;
    }

    putFont(font) {
        if (font.isAlreadyPutted !== true) {
            font.objectNumber = this.newObject();
            this.out("<<");
            this.out("/Type /Font");
            this.out("/BaseFont /" + this.toPDFName(font.postScriptName));
            this.out("/Subtype /Type1");
            if (typeof font.encoding === "string") {
                this.out("/Encoding /" + font.encoding);
            }
            this.out("/FirstChar 32");
            this.out("/LastChar 255");
            this.out(">>");
            this.out("endobj");
        }
    };

    putFonts() {
        for (var fontKey in this.fonts) {
            if (this.fonts.hasOwnProperty(fontKey)) {
                if (
                this.putOnlyUsedFonts === false ||
                (this.putOnlyUsedFonts === true && this.usedFonts.hasOwnProperty(fontKey))
                ) {
                    this.putFont(this.fonts[fontKey]);
                }
            }
        }
    };

    putXobjectDict() {
        this.out("/XObject <<");

        for (var xObjectKey in this.renderTargets) {
            if (renderTargets.hasOwnProperty(xObjectKey) && renderTargets[xObjectKey].objectNumber >= 0) {
                this.out("/" + xObjectKey + " " + renderTargets[xObjectKey].objectNumber + " 0 R");
            }
        } // Loop through images, or other data objects
        this.out(">>");
    }

    putResourceDictionary(objectIds) {
        this.newObjectDeferredBegin(objectIds.resourcesOid, true);
        this.out("<<");
        this.out("/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]");
        this.out("/XObject <<");
        this.putXObjectsDictCallback();
        this.out(">>");
        this.out(">>");
        this.out("endobj");
    };

    putResources() {
        this.putResourcesCallback();
        this.putResourceDictionary({
            resourcesOid: this.resourceDictionaryObjId,
            objectOid: Number.MAX_SAFE_INTEGER // output all objects
        });
    }

    putAdditionalObjects(){
        for (var i = 0; i < this.additionalObjects.length; i++) {
            var obj = this.additionalObjects[i];
            this.newObjectDeferredBegin(obj.objId, true);
            this.out(obj.content);
            this.out("endobj");
        }
    }

    to8bitStream(text, flags) {
        var i,
            l,
            sourceEncoding,
            encodingBlock,
            outputEncoding,
            newtext,
            isUnicode,
            ch,
            bch;

        flags = flags || {};
        sourceEncoding = flags.sourceEncoding || "Unicode";
        outputEncoding = flags.outputEncoding;

        // This 'encoding' section relies on font metrics format
        // attached to font objects by, among others,
        // "Willow Systems' standard_font_metrics plugin"
        // see jspdf.plugin.standard_font_metrics.js for format
        // of the font.metadata.encoding Object.
        // It should be something like
        //   .encoding = {'codePages':['WinANSI....'], 'WinANSI...':{code:code, ...}}
        //   .widths = {0:width, code:width, ..., 'fof':divisor}
        //   .kerning = {code:{previous_char_code:shift, ..., 'fof':-divisor},...}
        if (
        (flags.autoencode || outputEncoding) &&
        fonts[activeFontKey].metadata &&
        fonts[activeFontKey].metadata[sourceEncoding] &&
        fonts[activeFontKey].metadata[sourceEncoding].encoding
        ) {
            encodingBlock = fonts[activeFontKey].metadata[sourceEncoding].encoding;

            // each font has default encoding. Some have it clearly defined.
            if (!outputEncoding && fonts[activeFontKey].encoding) {
                outputEncoding = fonts[activeFontKey].encoding;
            }

            // Hmmm, the above did not work? Let's try again, in different place.
            if (!outputEncoding && encodingBlock.codePages) {
                outputEncoding = encodingBlock.codePages[0]; // let's say, first one is the default
            }

            if (typeof outputEncoding === "string") {
                outputEncoding = encodingBlock[outputEncoding];
            }
            // we want output encoding to be a JS Object, where
            // key = sourceEncoding's character code and
            // value = outputEncoding's character code.
            if (outputEncoding) {
                isUnicode = false;
                newtext = [];
                for (i = 0, l = text.length; i < l; i++) {
                    ch = outputEncoding[text.charCodeAt(i)];
                    if (ch) {
                        newtext.push(String.fromCharCode(ch));
                    } else {
                        newtext.push(text[i]);
                    }

                    // since we are looping over chars anyway, might as well
                    // check for residual unicodeness
                    if (newtext[i].charCodeAt(0) >> 8) {
                        /* more than 255 */
                        isUnicode = true;
                    }
                }
                text = newtext.join("");
            }
        }

        i = text.length;
        // isUnicode may be set to false above. Hence the triple-equal to undefined
        while (isUnicode === undefined && i !== 0) {
            if (text.charCodeAt(i - 1) >> 8) {
                /* more than 255 */
                isUnicode = true;
            }
            i--;
        }
        if (!isUnicode) {
            return text;
        }

        newtext = flags.noBOM ? [] : [254, 255];
        for (i = 0, l = text.length; i < l; i++) {
            ch = text.charCodeAt(i);
            bch = ch >> 8; // divide by 256
            if (bch >> 8) {
                /* something left after dividing by 256 second time */
                throw new Error(
                    "Character at position " +
                    i +
                    " of string '" +
                    text +
                    "' exceeds 16bits. Cannot be encoded into UCS-2 BE"
                );
            }
            newtext.push(bch);
            newtext.push(ch - (bch << 8));
        }
        return String.fromCharCode.apply(undefined, newtext);
    };

    pdfEscape(text,flags) {
        /**
         * Replace '/', '(', and ')' with pdf-safe versions
         *
         * Doing to8bitStream does NOT make this PDF display unicode text. For that
         * we also need to reference a unicode font and embed it - royal pain in the rear.
         *
         * There is still a benefit to to8bitStream - PDF simply cannot handle 16bit chars,
         * which JavaScript Strings are happy to provide. So, while we still cannot display
         * 2-byte characters property, at least CONDITIONALLY converting (entire string containing)
         * 16bit chars to (USC-2-BE) 2-bytes per char + BOM streams we ensure that entire PDF
         * is still parseable.
         * This will allow immediate support for unicode in document properties strings.
         */
        return this.to8bitStream(text, flags)
            .replace(/\\/g, "\\\\")
            .replace(/\(/g, "\\(")
            .replace(/\)/g, "\\)");
    }

    putXRef() {
        var p = "0000000000";

        this.out("xref");
        this.out("0 " + (this.objectNumber + 1));
        this.out("0000000000 65535 f ");
        for (var i = 1; i <= this.objectNumber; i++) {
            var offset = this.offsets[i];
            if (typeof offset === "function") {
                this.out((p + this.offsets[i]()).slice(-10) + " 00000 n ");
            } else {
                if (typeof this.offsets[i] !== "undefined") {
                    this.out((p + this.offsets[i]).slice(-10) + " 00000 n ");
                } else {
                    this.out("0000000000 00000 n ");
                }
            }
        }
    }

    putTrailer() {
        this.out("trailer");
        this.out("<<");
        this.out("/Size " + (this.objectNumber + 1));
        // Root and Info must be the last and second last objects written respectively
        this.out("/Root " + this.objectNumber + " 0 R");
        this.out("/Info " + (this.objectNumber - 1) + " 0 R");
        if (this.encryptionOptions !== null) {
            this.out("/Encrypt " + this.encryption.oid + " 0 R");
        }
        this.out("/ID [ <" + this.fileId + "> <" + this.fileId + "> ]");
        this.out(">>");
    }

    putCatalog(options) {
        options = options || {};
        var tmpRootDictionaryObjId =
            options.rootDictionaryObjId || this.rootDictionaryObjId;
        this.newObject();
        this.out("<<");
        this.out("/Type /Catalog");
        this.out("/Pages " + tmpRootDictionaryObjId + " 0 R");
        // PDF13ref Section 7.2.1
        if (!this.zoomMode) this.zoomMode = "fullwidth";
        switch (this.zoomMode) {
            case "fullwidth":
                this.out("/OpenAction [3 0 R /FitH null]");
                break;
            case "fullheight":
                this.out("/OpenAction [3 0 R /FitV null]");
                break;
            case "fullpage":
                this.out("/OpenAction [3 0 R /Fit]");
                break;
            case "original":
                this.out("/OpenAction [3 0 R /XYZ null null 1]");
                break;
            default:
                var pcn = "" + this.zoomMode;
                if (pcn.substr(pcn.length - 1) === "%")
                this.zoomMode = parseInt(this.zoomMode) / 100;
                if (typeof this.zoomMode === "number") {
                    this.out("/OpenAction [3 0 R /XYZ null null " + this.f2(this.zoomMode) + "]");
                }
        }
        if (!this.layoutMode) this.layoutMode = "continuous";
        switch (this.layoutMode) {
            case "continuous":
                this.out("/PageLayout /OneColumn");
                break;
            case "single":
                this.out("/PageLayout /SinglePage");
                break;
            case "two":
            case "twoleft":
                this.out("/PageLayout /TwoColumnLeft");
                break;
            case "tworight":
                this.out("/PageLayout /TwoColumnRight");
                break;
        }
        if (this.pageMode) {
            /**
             * A name object specifying how the document should be displayed when opened:
             * UseNone      : Neither document outline nor thumbnail images visible -- DEFAULT
             * UseOutlines  : Document outline visible
             * UseThumbs    : Thumbnail images visible
             * FullScreen   : Full-screen mode, with no menu bar, window controls, or any other window visible
             */
            this.out("/PageMode /" + this.pageMode);
        }
        this.out(">>");
        this.out("endobj");
    }

    getFilters() {
        return this.filters;
    }

    processDataByFilters(origData, filterChain) {
        var i = 0;
        var data = origData || "";
        var reverseChain = [];
        filterChain = filterChain || [];

        if (typeof filterChain === "string") {
            filterChain = [filterChain];
        }

        for (i = 0; i < filterChain.length; i += 1) {
            switch (filterChain[i]) {
                case "ASCII85Decode":
                case "/ASCII85Decode":
                    data = ASCII85Decode(data);
                    reverseChain.push("/ASCII85Encode");
                    break;
                case "ASCII85Encode":
                case "/ASCII85Encode":
                    data = ASCII85Encode(data);
                    reverseChain.push("/ASCII85Decode");
                    break;
                case "ASCIIHexDecode":
                case "/ASCIIHexDecode":
                    data = ASCIIHexDecode(data);
                    reverseChain.push("/ASCIIHexEncode");
                    break;
                case "ASCIIHexEncode":
                case "/ASCIIHexEncode":
                    data = ASCIIHexEncode(data);
                    reverseChain.push("/ASCIIHexDecode");
                    break;
                case "FlateEncode":
                case "/FlateEncode":
                    data = FlateEncode(data);
                    reverseChain.push("/FlateDecode");
                    break;
                default:
                    throw new Error(
                        'The filter: "' + filterChain[i] + '" is not implemented'
                    );
            }
        }

        return { data: data, reverseChain: reverseChain.reverse().join(" ") };
    }

    putStream(options) {
        options = options || {};
        var data = options.data || "";
        var filters = options.filters || this.getFilters();
        var alreadyAppliedFilters = options.alreadyAppliedFilters || [];
        var addLength1 = options.addLength1 || false;
        var valueOfLength1 = data.length;
        var objectId = options.objectId;
        var encryptor = function(data) {
            return data;
        };
        if (this.encryptionOptions !== null && typeof objectId == "undefined") {
            throw new Error(
                "ObjectId must be passed to putStream for file encryption"
            );
        }
        if (this.encryptionOptions !== null) {
            encryptor = encryption.encryptor(objectId, 0);
        }

        var processedData = {};
        if (filters === true) {
            filters = ["FlateEncode"];
        }
        var keyValues = options.additionalKeyValues || [];
        //TODO sreeni
        /*if (typeof jsPDF.API.processDataByFilters !== "undefined") {
          processedData = jsPDF.API.processDataByFilters(data, filters);
        } else {
          processedData = { data: data, reverseChain: [] };
        }*/
        processedData = this.processDataByFilters(data, filters);
        var filterAsString =
            processedData.reverseChain +
            (Array.isArray(alreadyAppliedFilters)
                ? alreadyAppliedFilters.join(" ")
                : alreadyAppliedFilters.toString());

        if (processedData.data.length !== 0) {
            keyValues.push({
                key: "Length",
                value: processedData.data.length
            });
            if (addLength1 === true) {
                keyValues.push({
                    key: "Length1",
                    value: valueOfLength1
                });
            }
        }

        if (filterAsString.length != 0) {
            if (filterAsString.split("/").length - 1 === 1) {
                keyValues.push({
                    key: "Filter",
                    value: filterAsString
                });
            } else {
                keyValues.push({
                    key: "Filter",
                    value: "[" + filterAsString + "]"
                });

                for (var j = 0; j < keyValues.length; j += 1) {
                    if (keyValues[j].key === "DecodeParms") {
                        var decodeParmsArray = [];

                        for (
                            var i = 0;
                            i < processedData.reverseChain.split("/").length - 1;
                            i += 1
                        ) {
                            decodeParmsArray.push("null");
                        }

                        decodeParmsArray.push(keyValues[j].value);
                        keyValues[j].value = "[" + decodeParmsArray.join(" ") + "]";
                    }
                }
            }
        }

        this.out("<<");
        for (var k = 0; k < keyValues.length; k++) {
            this.out("/" + keyValues[k].key + " " + keyValues[k].value);
        }
        this.out(">>");
        if (processedData.data.length !== 0) {
            this.out("stream");
            this.out(encryptor(processedData.data));
            this.out("endstream");
        }
    }

    putPage(page) {
        var pageNumber = page.number;
        var data = page.data;
        var pageObjectNumber = page.objId;
        var pageContentsObjId = page.contentsObjId;

        this.newObjectDeferredBegin(pageObjectNumber, true);
        this.out("<</Type /Page");
        this.out("/Parent " + page.rootDictionaryObjId + " 0 R");
        this.out("/Resources " + page.resourceDictionaryObjId + " 0 R");
        this.out(
            "/MediaBox [" +
            parseFloat(this.hpf(page.mediaBox.bottomLeftX)) +
            " " +
            parseFloat(this.hpf(page.mediaBox.bottomLeftY)) +
            " " +
            this.hpf(page.mediaBox.topRightX) +
            " " +
            this.hpf(page.mediaBox.topRightY) +
            "]"
        );
        if (page.cropBox !== null) {
            this.out(
                "/CropBox [" +
                this.hpf(page.cropBox.bottomLeftX) +
                " " +
                this.hpf(page.cropBox.bottomLeftY) +
                " " +
                this.hpf(page.cropBox.topRightX) +
                " " +
                this.hpf(page.cropBox.topRightY) +
                "]"
            );
        }

        if (page.bleedBox !== null) {
            this.out(
                "/BleedBox [" +
                this.hpf(page.bleedBox.bottomLeftX) +
                " " +
                this.hpf(page.bleedBox.bottomLeftY) +
                " " +
                this.hpf(page.bleedBox.topRightX) +
                " " +
                this.hpf(page.bleedBox.topRightY) +
                "]"
            );
        }

        if (page.trimBox !== null) {
            this.out(
                "/TrimBox [" +
                this.hpf(page.trimBox.bottomLeftX) +
                " " +
                this.hpf(page.trimBox.bottomLeftY) +
                " " +
                this.hpf(page.trimBox.topRightX) +
                " " +
                this.hpf(page.trimBox.topRightY) +
                "]"
            );
        }

        if (page.artBox !== null) {
            this.out(
                "/ArtBox [" +
                this.hpf(page.artBox.bottomLeftX) +
                " " +
                this.hpf(page.artBox.bottomLeftY) +
                " " +
                this.hpf(page.artBox.topRightX) +
                " " +
                this.hpf(page.artBox.topRightY) +
                "]"
            );
        }

        if (typeof page.userUnit === "number" && page.userUnit !== 1.0) {
            this.out("/UserUnit " + page.userUnit);
        }
        this.out("/Contents " + pageContentsObjId + " 0 R");
        this.out(">>");
        this.out("endobj");
        // Page content
        var pageContent = data.join("\n");

        if (this.apiMode === ApiMode.ADVANCED) {
            // if the user forgot to switch back to COMPAT mode, we must balance the graphics stack again
            pageContent += "\nQ";
        }

        this.newObjectDeferredBegin(pageContentsObjId, true);
        this.putStream({
            data: pageContent,
            filters: this.getFilters(),
            objectId: pageContentsObjId
        });
        this.out("endobj");
        return pageObjectNumber;
    }

    putPages() {
        var n,
            i,
            pageObjectNumbers = [];

        for (n = 1; n <= this.page; n++) {
            this.pagesContext[n].objId = this.newObjectDeferred();
            this.pagesContext[n].contentsObjId = this.newObjectDeferred();
        }

        for (n = 1; n <= this.page; n++) {
            pageObjectNumbers.push(
            this.putPage({
                number: n,
                data: this.pages[n],
                objId: this.pagesContext[n].objId,
                contentsObjId: this.pagesContext[n].contentsObjId,
                mediaBox: this.pagesContext[n].mediaBox,
                cropBox: this.pagesContext[n].cropBox,
                bleedBox: this.pagesContext[n].bleedBox,
                trimBox: this.pagesContext[n].trimBox,
                artBox: this.pagesContext[n].artBox,
                userUnit: this.pagesContext[n].userUnit,
                rootDictionaryObjId: this.rootDictionaryObjId,
                resourceDictionaryObjId: this.resourceDictionaryObjId
            })
            );
        }
        this.newObjectDeferredBegin(this.rootDictionaryObjId, true);
        this.out("<</Type /Pages");
        var kids = "/Kids [";
        for (i = 0; i < this.page; i++) {
            kids += pageObjectNumbers[i] + " 0 R ";
        }
        this.out(kids + "]");
        this.out("/Count " + this.page);
        this.out(">>");
        this.out("endobj");
    }

    putInfo() {
        var objectId = this.newObject();
        var encryptor = function(data) {
            return data;
        };
        if (this.encryptionOptions !== null) {
            encryptor = encryption.encryptor(objectId, 0);
        }
        this.out("<<");
        this.out("/Producer (" + this.pdfEscape(encryptor("jsPDF " + this.version)) + ")");
        for (var key in this.documentProperties) {
            if (this.documentProperties.hasOwnProperty(key) && this.documentProperties[key]) {
                out(
                    "/" +
                    key.substr(0, 1).toUpperCase() +
                    key.substr(1) +
                    " (" +
                    pdfEscape(encryptor(documentProperties[key])) +
                    ")"
                );
            }
        }
        this.out("/CreationDate (" + this.pdfEscape(encryptor(this.creationDate)) + ")");
        this.out(">>");
        this.out("endobj");
    }
    getArrayBuffer(data) {
        var len = data.length,
            ab = new ArrayBuffer(len),
            u8 = new Uint8Array(ab);

        while (len--) u8[len] = data.charCodeAt(len);
        return ab;
    }

    resetDocument(){
        //reset fields relevant for objectNumber generation and xref.
        this.objectNumber = 0;
        this.contentLength = 0;
        this.content = [];
        this.offsets = [];
        this.additionalObjects = [];

        this.rootDictionaryObjId = this.newObjectDeferred();
        this.resourceDictionaryObjId = this.newObjectDeferred();
    }

    buildDocument() {
        this.resetDocument();
        this.setOutputDestination(this.content);

        this.putHeader();
        this.putPages();
        this.putAdditionalObjects();
        this.putResources();
        if (this.encryptionOptions !== null) putEncryptionDict();
        this.putInfo();
        this.putCatalog();

        var offsetOfXRef = this.contentLength;
        this.putXRef();
        this.putTrailer();
        this.out("startxref");
        this.out("" + offsetOfXRef);
        this.out("%%EOF");

        this.setOutputDestination(this.pages[this.currentPage]);

        return this.content.join("\n");
    }

    putImage(image) {

        var filter = this.getFilters();
        while (filter.indexOf("FlateEncode") !== -1) {
            filter.splice(filter.indexOf("FlateEncode"), 1);
        }

        image.objectId = this.newObject();

        var additionalKeyValues = [];
        additionalKeyValues.push({ key: "Type", value: "/XObject" });
        additionalKeyValues.push({ key: "Subtype", value: "/Image" });
        additionalKeyValues.push({ key: "Width", value: image.width });
        additionalKeyValues.push({ key: "Height", value: image.height });

        if (image.colorSpace === this.color_spaces.INDEXED) {
            additionalKeyValues.push({
                key: "ColorSpace",
                value:
                "[/Indexed /DeviceRGB " +
                // if an indexed png defines more than one colour with transparency, we've created a sMask
                (image.palette.length / 3 - 1) +
                " " +
                ("sMask" in image && typeof image.sMask !== "undefined"
                    ? image.objectId + 2
                    : image.objectId + 1) +
                " 0 R]"
            });
        } else {
            additionalKeyValues.push({
                key: "ColorSpace",
                value: "/" + image.colorSpace
            });
            if (image.colorSpace === this.color_spaces.DEVICE_CMYK) {
                additionalKeyValues.push({ key: "Decode", value: "[1 0 1 0 1 0 1 0]" });
            }
        }
        additionalKeyValues.push({
            key: "BitsPerComponent",
            value: image.bitsPerComponent
        });
        if (
        "decodeParameters" in image &&
        typeof image.decodeParameters !== "undefined"
        ) {
            additionalKeyValues.push({
                key: "DecodeParms",
                value: "<<" + image.decodeParameters + ">>"
            });
        }
        if ("transparency" in image && Array.isArray(image.transparency)) {
            var transparency = "",
                i = 0,
                len = image.transparency.length;
            for (; i < len; i++)
            transparency +=
            image.transparency[i] + " " + image.transparency[i] + " ";

            additionalKeyValues.push({
                key: "Mask",
                value: "[" + transparency + "]"
            });
        }
        if (typeof image.sMask !== "undefined") {
            additionalKeyValues.push({
                key: "SMask",
                value: image.objectId + 1 + " 0 R"
            });
        }

        var alreadyAppliedFilters =
                typeof image.filter !== "undefined" ? ["/" + image.filter] : undefined;

        this.putStream({
            data: image.data,
            additionalKeyValues: additionalKeyValues,
            alreadyAppliedFilters: alreadyAppliedFilters,
            objectId: image.objectId
        });

        this.write("endobj");

        // Soft mask
        if ("sMask" in image && typeof image.sMask !== "undefined") {
            var decodeParameters =
                "/Predictor " +
                image.predictor +
                " /Colors 1 /BitsPerComponent " +
                image.bitsPerComponent +
                " /Columns " +
                image.width;
            var sMask = {
                width: image.width,
                height: image.height,
                colorSpace: "DeviceGray",
                bitsPerComponent: image.bitsPerComponent,
                decodeParameters: decodeParameters,
                data: image.sMask
            };
            if ("filter" in image) {
                sMask.filter = image.filter;
            }
            this.putImage(sMask);
        }

        //Palette
        if (image.colorSpace === this.color_spaces.INDEXED) {
            var objId = this.internal.newObject();
            //out('<< /Filter / ' + img['f'] +' /Length ' + img['pal'].length + '>>');
            //putStream(zlib.compress(img['pal']));
            this.putStream({
                data: arrayBufferToBinaryString(new Uint8Array(image.palette)),
                objectId: objId
            });
            this.write("endobj");
        }
    };
    putResourcesCallback() {
        var images = this.collections[this.namespace + "images"];
        for (var i in images) {
            this.putImage(images[i]);
        }
    }

    putXObjectsDictCallback() {
        var images = this.collections[this.namespace + "images"],
            image;

        for (var i in images) {
            image = images[i];
            this.write("/I" + image.index, image.objectId, "0", "R");
        }
    }
}

export {jsPDF}